const Pipe = @This();

lock: os.Lock.Spin = .init("pipe"),
data: [SIZE]u8 = @splat(0),
/// number of bytes read
nread: usize = 0,
/// number of bytes written
nwrite: usize = 0,
/// read fd is still open
readopen: bool = true,
/// write fd is still open
writeopen: bool = true,

pub const SIZE = 512;

/// Allocates a new pipe and returns its read/write file handles.
pub fn alloc() !struct { *File, *File } {
    const r_file = try File.alloc();
    errdefer r_file.close();

    const w_file = try File.alloc();
    errdefer w_file.close();

    const pipe = try gpa.create(Pipe);
    pipe.* = .{};

    r_file.readable = true;
    r_file.writable = false;
    r_file.data = .{ .pipe = pipe };

    w_file.readable = false;
    w_file.writable = true;
    w_file.data = .{ .pipe = pipe };

    return .{ r_file, w_file };
}

pub fn close(self: *Pipe, writable: bool) void {
    self.lock.acquire();
    if (writable) {
        self.writeopen = false;
        proc.wakeup(&self.nread);
    } else {
        self.readopen = false;
        proc.wakeup(&self.nwrite);
    }
    const free_pipe = !self.readopen and !self.writeopen;
    self.lock.release();

    if (free_pipe) gpa.destroy(self);
}

pub fn write(self: *Pipe, addr: usize, len: usize) !usize {
    var stack: [32]u8 = undefined;
    const large = len > stack.len;
    const buffer: []u8 = if (large) try gpa.alloc(u8, os.PGSIZE) else &stack;
    defer if (large) gpa.free(buffer);

    const p = proc.current().?;
    var reader = p.reader(.auto, buffer, @ptrFromInt(addr));

    self.lock.acquire();
    defer self.lock.release();

    var i: usize = 0;
    while (i < len) {
        if (!self.readopen) return error.BrokenPipe;
        if (p.isKilled()) return error.ProcessNotFound;

        if (self.nwrite == self.nread + SIZE) { // pipe full
            proc.wakeup(&self.nread);
            p.sleep(&self.nwrite, &self.lock);
        } else {
            const char = reader.io_r.takeByte() catch break;
            self.data[self.nwrite % SIZE] = char;
            self.nwrite += 1;
            i += 1;
        }
    }

    proc.wakeup(&self.nread);
    return i;
}

pub fn read(self: *Pipe, addr: usize, len: usize) !usize {
    const p = proc.current().?;

    self.lock.acquire();
    defer self.lock.release();

    while (self.nread == self.nwrite and self.writeopen) { // pipe empty
        if (p.isKilled()) return error.ProcessNotFound;
        p.sleep(&self.nread, &self.lock);
    }

    var i: usize = 0;
    var writer = p.writer(.auto, &.{}, @ptrFromInt(addr));
    while (i < len) : (i += 1) { // copy
        if (self.nread == self.nwrite) break;
        const char = self.data[self.nread % SIZE];
        writer.io_w.writeByte(char) catch break;
        self.nread += 1;
    }

    proc.wakeup(&self.nwrite);
    return i;
}

const std = @import("std");
const gpa = std.heap.page_allocator;
const os = @import("../os.zig");
const proc = os.proc;
const File = @import("File.zig");
